package com.jintian.smart.kernel.orm.mapper.internal;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.jintian.smart.kernel.common.util.ObjectUtil;
import com.jintian.smart.kernel.common.util.StringUtil;
import com.jintian.smart.kernel.core.annotation.orm.Condition;
import com.jintian.smart.kernel.core.domain.PageQuery;
import com.jintian.smart.kernel.core.orm.ISimpleCondition;
import org.beetl.core.GroupTemplate;
import org.beetl.core.Template;
import org.beetl.core.resource.StringTemplateResourceLoader;
import org.beetl.sql.core.SQLManager;
import org.beetl.sql.core.engine.template.BeetlTemplateEngine;
import org.beetl.sql.core.page.DefaultPageRequest;
import org.beetl.sql.core.page.PageRequest;
import org.beetl.sql.core.page.PageResult;
import org.beetl.sql.core.query.LambdaQuery;
import org.beetl.sql.mapper.MapperInvoke;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;

public class SimplePageQueryAMI extends MapperInvoke {
    private final static StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader() {
        /**
         *
         */
        private static final long serialVersionUID = 1L;

        @Override
        public boolean isCacheScript() {
            return false;
        }
    };

    @SuppressWarnings({"rawtypes"})
    @Override
    public Object call(SQLManager sm, Class entityClass, Method m, Object[] args) {
        PageRequest request = (PageRequest) args[0];
        if (request == null) {
            request = DefaultPageRequest.of(1, 10);
        }
        ISimpleCondition simpleCondition = null;
        if (request instanceof PageQuery) {
            simpleCondition = ((PageQuery) request).getSimpleCondition();
        }
        return SimplePageQueryAMI.execute(sm, entityClass, request, simpleCondition);
    }

    public static PageResult<?> execute(SQLManager sm, Class<?> entityClass, PageRequest<?> request, ISimpleCondition simpleCondition) {
        LambdaQuery<?> query = sm.lambdaQuery(entityClass);
        parseToQuery(simpleCondition, request, query);
        return query.page(request);
    }

    public static PageResult<?> execute(SQLManager sm, Class<?> entityClass, String tableName,
                                        PageRequest<?> request, ISimpleCondition simpleCondition) {
        return execute(sm, entityClass, tableName, request, simpleCondition, null);
    }

    public static PageResult<?> execute(SQLManager sm, Class<?> entityClass, String tableName,
                                        PageRequest<?> request, ISimpleCondition simpleCondition, Collection<?> params) {
        LambdaQuery<?> query = sm.lambdaQuery(tableName);
        if (params != null) {
            query.addParam(params);
        }
        parseToQuery(simpleCondition, request, query);
        Class<?> retClass = entityClass == null ? Map.class : entityClass;
        return query.page(request, retClass);
    }

    private static void parseToQuery(ISimpleCondition simpleCondition, PageRequest<?> request, LambdaQuery<?> query) {
        if (simpleCondition != null) {
            if (simpleCondition instanceof PageQuery.MapSimpleCondition) {
                Map<String, Object> template = (PageQuery.MapSimpleCondition) simpleCondition;
                for (Entry<String, Object> e : template.entrySet()) {
                    if (e.getValue() != null) {
                        query.andEq(query.getColName(e.getKey()), e.getValue());
                    }
                }
            } else {
                SimplePageQueryAMI.build(query, simpleCondition);
            }
        }
        if (StringUtil.isNotBlank(request.getOrderBy())) {
            String[] orders = request.getOrderBy().split(",");
            for (String str : orders) {
                String order = str.trim();
                int idx = order.indexOf(" ");
                String propName = idx > 0 ? order.substring(0, idx) : order;
                boolean desc = order.toLowerCase().contains("desc");
                query.order(query.getColName(propName), !desc);
            }
        }
    }

    private static int getQuestionMarkCount(String condition) {
        int s = 0;
        int cnt = 0;
        int idx = -1;
        while ((idx = condition.indexOf("?", s)) >= 0) {
            cnt++;
            s = idx + 1;
        }
        return cnt;
    }

    private static void build(LambdaQuery<?> query, Object simpleCondition) {
        org.springframework.util.ReflectionUtils.doWithFields(simpleCondition.getClass(), new FieldCallback() {
            @SuppressWarnings("rawtypes")
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                field.setAccessible(true);
                Condition condition = field.getAnnotation(Condition.class);
                String column = new StringUtil().firstNonBlank(condition.column(), query.getColName(field.getName()));
                Object value = field.get(simpleCondition);
                if (StringUtil.isNoneBlank(condition.custom())) {
                    if (ObjectUtil.isNotEmpty(value) || !condition.ignoreEmptyValue()) {
                        String custom = condition.custom().trim();
                        if (custom.contains("${") && custom.contains("}")) {
                            custom = calcCustomCondtionExpr(query.sqlManager, custom, simpleCondition);
                        }
                        int cnt = getQuestionMarkCount(custom);
                        Object[] params = new Object[cnt];
                        Arrays.fill(params, value);
                        query.and(custom, params);
                    }
                } else if (condition.fuzzy()) {
                    if (field.getType() == String.class) {
                        field.setAccessible(true);
                        query.andLike(column, Optional.ofNullable(value));
                    }
                } else if (condition.type() == Condition.TYPE_IN) {
                    query.andIn(column, Optional.ofNullable((Collection) value));
                } else if (condition.type() == Condition.TYPE_GENERAL) {
                    query.andEq(column, Optional.ofNullable(value));
                } else if (condition.type() == Condition.TYPE_VALUE_BETWEEN) {
                    String name = column;
                    Object minValue = null, maxValue = null;
                    {
                        String minName = name + "Min";
                        Field minField = ReflectionUtils.findField(simpleCondition.getClass(), minName);
                        if (minField != null) {
                            minField.setAccessible(true);
                            minValue = ReflectionUtils.getField(minField, simpleCondition);
                        }
                    }

                    {
                        String maxName = name + "Max";
                        Field maxField = ReflectionUtils.findField(simpleCondition.getClass(), maxName);
                        if (maxField != null) {
                            maxField.setAccessible(true);
                            maxValue = ReflectionUtils.getField(maxField, simpleCondition);
                        }
                    }
                    if (minValue != null && maxValue == null) {
                        query.andGreatEq(column, minValue);
                    } else if (minValue == null && maxValue != null) {
                        query.andLessEq(column, maxValue);
                    } else if (minValue != null && maxValue != null) {
                        query.andBetween(column, minValue, maxValue);
                    }
                }
            }
        }, new FieldFilter() {
            @Override
            public boolean matches(Field field) {
                return field.getAnnotation(Condition.class) != null;
            }
        });
    }

    private static String calcCustomCondtionExpr(SQLManager sqlManager, String expr, Object bean) {
        GroupTemplate groupTemplate = ((BeetlTemplateEngine) sqlManager.getSqlTemplateEngine()).getBeetl().getGroupTemplate();
        Map<String, Object> paras = new ObjectMapper().convertValue(bean, HashMap.class);
        Template tempalte = groupTemplate.getTemplate(expr, resourceLoader);
        tempalte.binding(paras);
        String conditon = tempalte.render();
        return conditon;
    }

    public static void main(String[] args) {
        System.out.println(SimplePageQueryAMI.getQuestionMarkCount("? ? ? ?"));
    }
}
